/*********************************************************************************
 *Copyright (C), 2016-2023, zhangjiance Tech. Co., Ltd.
 *FileName:  https_client.cpp
 *Author:  JianCe.Zhang
 *Version:  V1.0
 *Date: 2023-06-14
 *Description:
 *Function List:
 * 1. void function(void)
 * 2.
 * 3.
 * 4.
 *History:
 * 1. 2023-06-14;JianCe.Zhang;Init Function
 * 2.
 * 3.
 * 4.
 ************************************************************************************/
/***************************************Includes***********************************/
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include "https_client.h"

/****************************************Macros*************************************/

/***************************************Variables***********************************/

/***************************************Functions***********************************/

using namespace std;

namespace zhangjiance
{
    namespace upgradeservice
    {

        /*******************************************************************************
         * @name HttpsDownloadTask::HttpsDownloadTask
         * @brief Constructor of HttpsDownloadTask, initialize member variables, create curl handle
         * @brief HttpsDownloadTask
         * @param[in] url: Download link
         * @param[in] path: Path to save the downloaded file
         * @param[in] isFsyncEnable: Whether to enable fsync
         * @retval None
         * @author  ZhangJianCe
         *******************************************************************************/
        HttpsDownloadTask::HttpsDownloadTask(const std::string url, const std::string path, bool isFsyncEnable)
            : m_curl(nullptr), m_file(nullptr), m_downloadThread(nullptr), m_lastDownloadProgress(0)
        {
            m_downloadUrl = url;
            m_downloadPath = path;
            cout << "m_downloadUrl = " << m_downloadUrl << endl;
            cout << "m_downloadPath = " << m_downloadPath << endl;
            m_isFsyncEnabled = isFsyncEnable;
            m_downloadTimeout = 600;
            m_connectTimeout = 10;
            m_downloadStatus = DownloadStatus::Init;
            static_cast<void>(HttpsDownloadTask::init());
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::~HttpsDownloadTask
         * @brief Destructor of HttpsDownloadTask, pause the download first, then release resources
         * @brief HttpsDownloadTask
         * @param[in] None
         * @param[out] None
         * @retval None
         * @author  ZhangJianCe
         *******************************************************************************/
        HttpsDownloadTask::~HttpsDownloadTask()
        {
            cout << "HttpsDownloadTask::~HttpsDownloadTask() " << endl;
            static_cast<void>(HttpsDownloadTask::pause());
            if (nullptr!=m_file)
            {
                curl_easy_cleanup(m_curl);
                m_file = nullptr;
            }
            if (nullptr!=m_file)
            {
                static_cast<void>(fflush(m_file));
                static_cast<void>(fsync(fileno(m_file)));
                static_cast<void>(fclose(m_file));
                m_file = nullptr;
            }
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::init
         * @brief Initialize libcurl, setopt to set download options
         * @param[in] None
         * @param[out] None
         * @retval false: Initialization failed true: Initialization succeeded
         * @author  ZhangJianCe
         *******************************************************************************/
        bool HttpsDownloadTask::init()
        {
            cout << "HttpsDownloadTask::init() " << endl;
            // Initialize libcurl
            m_curl = curl_easy_init();
            curl_easy_setopt(m_curl, CURLOPT_PRIVATE, this);
            if (nullptr == m_curl)
            {
                cout << "curl_easy_init() fail " << endl;
                return false;
            }
            struct curl_slist *header_list = nullptr;
            header_list = curl_slist_append(header_list, "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko");
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, header_list));
            // static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"));
            // Set download progress callback function
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, &HttpsDownloadTask::progressCallback));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, this));
            // Set download completion callback function
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, &HttpsDownloadTask::writeCallback));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this));
            // Set receive buffer size
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_BUFFERSIZE, 100000));
            // Set download options
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_CIPHER_LIST, "TLSv1.2"));
            // When the value of CURLOPT_VERBOSE is 1, detailed debugging information will be displayed
            // static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1));
            // Disable SIGPIPE signal to prevent the program from crashing when curl_easy_perform returns in poor network conditions
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1));
            // Set TCP keepalive, send a heartbeat packet every 60 seconds
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPIDLE, 60));
            // If the speed is lower than 60B/s and exceeds 10 seconds, disconnect
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_TIME, 10));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_LOW_SPEED_LIMIT, 60));
            // Set the CURLOPT_FAILONERROR option, HTTP status codes above 400 will return an error
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_FAILONERROR, 1));

            cout << "init curl success " << endl;
            return true;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::download
         * @brief Depending on whether the file exists, choose to resume from breakpoint or re-download, and start the download thread
         * @param[in] None
         * @retval false: Download failed true: Download succeeded
         * @author  ZhangJianCe
         *******************************************************************************/
        bool HttpsDownloadTask::download()
        {
            cout << "m_downloadUrl = " << m_downloadUrl << endl;
            // Open file
            if (m_downloadPath.empty())
            {
                cout << "download path is empty " << endl;
                return false;
            }

            cout << "check and try open: " << m_downloadPath << endl;
            if (0 == access(m_downloadPath.c_str(), F_OK))
            {
                struct stat st;
                if (0 == stat(m_downloadPath.c_str(), &st))
                {
                    m_resumeFrom = static_cast<long long>(st.st_size);
                    m_file = fopen(m_downloadPath.c_str(), "ab");
                    cout << "File exists, size is " << m_resumeFrom << " bytes." << endl;
                }
                else
                {
                    m_resumeFrom = 0;
                    m_file = fopen(m_downloadPath.c_str(), "wb");
                    cout << "File exists,but can't get size,creating new file." << endl;
                }
            }
            else
            {
                m_resumeFrom = 0;
                cout << "File doesn't exist, creating new file." << endl;
                m_file = fopen(m_downloadPath.c_str(), "wb");
            }

            if (nullptr == m_file)
            {
                cout << "open " << m_downloadPath << " fail " << endl;
                return false;
            }

            // Set download link
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_URL, m_downloadUrl.c_str()));
            // Create a new thread and start downloading
            m_downloadThread = std::make_unique<std::thread>(&HttpsDownloadTask::downloadThreadFunc, this).release();
            cout << "m_downloadThread->detach() " << endl;

            return true;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::pause
         * @brief Change download status, terminate download thread, release curl resources, close file
         * @param[in] None
         * @retval false: Pause failed true: Pause succeeded
         * @author  ZhangJianCe
         *******************************************************************************/
        bool HttpsDownloadTask::pause()
        {
            cout << "HttpsDownloadTask::pause() " << endl;
            // Pause download, not available below libcurl 7.18.0
            // CURLcode res = curl_easy_pause(m_curl, CURLPAUSE_ALL);
            m_downloadStatus = DownloadStatus::Paused;
            // Terminate download thread
            bool ret = m_downloadThread->joinable();
            if ((nullptr != m_downloadThread) && (true == ret))
            {
                cout << "m_downloadThread->join() start " << endl;
                m_downloadThread->join();
                m_downloadThread = nullptr;
                cout << "m_downloadThread->join() end " << endl;
            }
            // Release curl resources
            if (nullptr != m_curl)
            {
                curl_easy_cleanup(m_curl);
                cout << "curl_easy_cleanup(m_curl) " << endl;
                m_curl = nullptr;
            }

            // Close file
            if (nullptr!=m_file)
            {
                static_cast<void>(fflush(m_file));
                static_cast<void>(fsync(fileno(m_file)));
                static_cast<void>(fclose(m_file));
                m_file = nullptr;
                cout << "fclose(m_file) " << endl;
            }
            return true;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::resume
         * @brief Change download status, reinitialize curl, start download thread
         * @param[in] None
         * @param[out] None
         * @retval false: Resume failed true: Resume succeeded
         * @author  ZhangJianCe
         *******************************************************************************/
        bool HttpsDownloadTask::resume()
        {
            m_downloadStatus = DownloadStatus::Downloading;
            static_cast<void>(init());
            static_cast<void>(download());
            cout << "HttpsDownloadTask::resume() " << endl;
            return true;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::cancel
         * @brief Change download status, terminate download thread, delete download file
         * @param[in] None
         * @param[out] None
         * @retval false: Cancel failed true: Cancel succeeded
         * @author  ZhangJianCe
         *******************************************************************************/
        bool HttpsDownloadTask::cancel()
        {
            cout << "HttpsDownloadTask::cancel() " << endl;
            static_cast<void>(pause());
            m_downloadStatus = DownloadStatus::Canceled;
            if (access(m_downloadPath.c_str(), F_OK) == 0)
            {
                static_cast<void>(remove(m_downloadPath.c_str()));
                cout << "remove(m_downloadPath.c_str()) " << endl;
            }
            return true;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::setOptions
         * @brief Set download options: download callback function, download speed limit, connection timeout, download timeout
         * @param[in] callback : Download callback function
         * @param[in] speedLimit : Download speed limit, unit B/s
         * @param[in] connecttimeout : Connection timeout, unit s
         * @param[in] downloadtimeout : Download timeout, unit s
         * @retval None
         * @author  ZhangJianCe
         *******************************************************************************/
        void HttpsDownloadTask::setOptions(std::function<void(int, DownloadStatus)> callback, long speedLimit, int connecttimeout, int downloadtimeout)
        {
            cout << "m_downloadUrl = " << m_downloadUrl << endl;
            // Set download options
            m_downloadSpeedLimit = speedLimit;
            m_connectTimeout = connecttimeout;
            m_downloadTimeout = downloadtimeout;
            m_downloadCallback = callback;
            // Set download speed limit, unit B/s
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)speedLimit));
            cout << "set speed limit: " << speedLimit << " B/s " << endl;
            // Set connection timeout, unit s
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, m_connectTimeout));
            cout << "set connect timeout: " << m_connectTimeout << " s " << endl;
            // Set download timeout, unit s
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, m_downloadTimeout));
            cout << "set download timeout: " << m_downloadTimeout << " s " << endl;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::setSslVerifyPeer
         * @brief Set SSL verification switch
         * @param[in] verifyPeer : SSL verification switch
         * @param[out] None
         * @retval None
         * @author ZhangJianCe
         *******************************************************************************/
        void HttpsDownloadTask::setSslVerifyPeer(bool verifyPeer)
        {
            if (verifyPeer)
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1));
                cout << "set ssl verify peer " << endl;
            }
            else
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0));
                cout << "set ssl not verify peer " << endl;
            }
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::setSslContext
         * @brief Set client certificate and private key as well as server certificate chain
         * @param[in] ClientCert : Client certificate path
         * @param[in] ClientKey : Client private key path
         * @param[in] CaFile : Server certificate chain file path
         * @param[in] CaPath : Server certificate chain directory path
         * @param[out] None
         * @retval None
         * @author ZhangJianCe
         *******************************************************************************/
        void HttpsDownloadTask::setSslContext(const std::string &ClientCert,
                                              const std::string &ClientKey,
                                              const std::string &CaFile,
                                              const std::string &CaPath)
        {
            if (ClientCert.empty())
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSLCERT, nullptr));
                cout << "set ClientCert path: NULL \r\n"
                     << endl;
            }
            else
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSLCERT, ClientCert.c_str()));
                cout << "set ClientCert path: " << ClientCert << endl;
            }
            if (ClientKey.empty())
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSLKEY, nullptr));
                cout << "set ClientKey path: NULL \r\n"
                     << endl;
            }
            else
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSLKEY, ClientKey.c_str()));
                cout << "set ClientKey path: " << ClientKey << endl;
            }
            if (CaFile.empty())
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_CAINFO, nullptr));
                cout << "set CaFile path: NULL \r\n"
                     << endl;
            }
            else
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_CAINFO, CaFile.c_str()));
                cout << "set CaFile path: " << CaFile << endl;
            }
            if (CaPath.empty())
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_CAPATH, nullptr));
                cout << "set CaPath path: NULL \r\n"
                     << endl;
            }
            else
            {
                static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_CAPATH, CaPath.c_str()));
                cout << "set CaPath path: " << CaPath << endl;
            }
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::sslctx_function
         * @brief SSL callback function
         * @param[in] curl : curl handle
         * @param[in] sslctx : SSL context
         * @param[in] parm : Callback function parameter
         * @param[out] None
         * @retval None
         * @author ZhangJianCe
         *******************************************************************************/
        CURLcode HttpsDownloadTask::sslctx_function(CURL *curl, void *sslctx, void *parm)
        {
            void *p = nullptr;
            curl_easy_getinfo(curl, CURLINFO_PRIVATE, &p);
            HttpsDownloadTask *self = static_cast<HttpsDownloadTask *>(p);
            return self->ssl_callback(self->m_curl, sslctx, parm);
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::setSslContextCallback
         * @brief Set SSL callback function
         * @param[in] callback : SSL callback function
         * @param[in] parm : Callback function parameter
         * @param[out] None
         * @retval None
         * @author ZhangJianCe
         *******************************************************************************/
        void HttpsDownloadTask::setSslContextCallback(std::function<CURLcode(CURL *curl, void *sslctx, void *parm)> callback, void *parm)
        {
            ssl_callback = move(callback);
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, &HttpsDownloadTask::sslctx_function));
            static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, parm));
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::downloadThreadFunc
         * @brief Download thread function, get the file size from the cloud,
         * set breakpoint continuation, send HTTP request and download files
         * @param[in] None
         * @param[out] None
         * @retval None
         * @author ZhangJianCe
         *******************************************************************************/
        void HttpsDownloadTask::downloadThreadFunc(void)
        {
            prctl(PR_SET_NAME, "Https_Download", 0, 0, 0);
            CURLcode res = CURLE_OK;
            long long filesize = 0;
            int retryCount = 0;
            while (retryCount < HTTP_RETRY_COUNT)
            {
                // Check if the file exists and get the current local file size
                if (0 == access(m_downloadPath.c_str(), F_OK))
                {
                    struct stat st;
                    if (0 == stat(m_downloadPath.c_str(), &st))
                    {
                        m_resumeFrom = static_cast<long long>(st.st_size);
                    }
                }

                // If the file size is 0, get the remote file size
                if (filesize <= 0)
                {
                    static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1)); // Set to not download the file, only get the file size
                    res = curl_easy_perform(m_curl);
                    static_cast<void>(curl_easy_getinfo(m_curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &filesize));
                    if (CURLE_OK != res)
                    {
                        cout << "Download Status, httpstatus: " << m_httpstatus << " error: " << curl_easy_strerror(res) << endl;
                    }

                    // If getting the file size fails, retry
                    if ((CURLE_UNSUPPORTED_PROTOCOL == res) || (CURLE_URL_MALFORMAT == res) || (CURLE_REMOTE_ACCESS_DENIED == res) || (CURLE_WRITE_ERROR == res) || (CURLE_HTTP_RETURNED_ERROR == res) || (CURLE_SSL_CONNECT_ERROR == res) || (m_httpstatus >= 400) || (m_downloadStatus == DownloadStatus::Canceled) || (m_downloadStatus == DownloadStatus::Paused))
                    {
                        break;
                    }
                    else if (CURLE_OK == res)
                    {
                        cout << "remote filesize: " << filesize << " bytes " << endl;
                        m_fileSize = filesize;
                    }
                    else
                    {
                        cout << "get remote filesize fail, retrying... " << endl;
                        std::this_thread::sleep_for(std::chrono::seconds(3));
                        retryCount++;
                        continue;
                    }
                }

                // If the file has already been downloaded, exit directly
                if (filesize == m_resumeFrom)
                {
                    m_downloadProgress = 100;
                    cout << "file already download finished " << endl;
                    break;
                }
                else
                {
                    static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_NOBODY, 0));
                    static_cast<void>(curl_easy_setopt(m_curl, CURLOPT_RESUME_FROM, m_resumeFrom));
                    res = curl_easy_perform(m_curl); // Send HTTP request and download the file
                    if (CURLE_OK != res)
                    {
                        cout << "Download Status, httpstatus: " << m_httpstatus << " error: " << curl_easy_strerror(res) << endl;
                    }
                    /*
                     * The following cases do not require retrying, terminate the thread directly
                     * CURLE_OK: Download successful
                     * CURLE_UNSUPPORTED_PROTOCOL: Unsupported protocol
                     * CURLE_URL_MALFORMAT: URL format error
                     * CURLE_REMOTE_ACCESS_DENIED: Access denied
                     * CURLE_WRITE_ERROR: Failed to write to the local file
                     * CURLE_HTTP_RETURNED_ERROR: HTTP returned an error
                     * CURLE_SSL_CONNECT_ERROR: SSL connection failed
                     */
                    if ((CURLE_OK == res) || (CURLE_UNSUPPORTED_PROTOCOL == res) || (CURLE_URL_MALFORMAT == res) || (CURLE_REMOTE_ACCESS_DENIED == res) || (CURLE_WRITE_ERROR == res) || (CURLE_HTTP_RETURNED_ERROR == res) || (CURLE_SSL_CONNECT_ERROR == res) || (m_httpstatus >= 400) || (m_downloadStatus == DownloadStatus::Canceled) || (m_downloadStatus == DownloadStatus::Paused))
                    {
                        break;
                    }

                    // Download failed, wait for 3 seconds and retry
                    std::this_thread::sleep_for(std::chrono::seconds(3));
                    cout << "download failed, retrying... " << endl;
                    retryCount++;
                }
            }

            // Close the file
            if (nullptr != m_file)
            {
                static_cast<void>(fflush(m_file));
                static_cast<void>(fsync(fileno(m_file)));
                static_cast<void>(fclose(m_file));
                m_file = nullptr;
                cout << "fclose(m_file) " << endl;
            }

            // Check the download result
            if (CURLE_OK != res)
            {
                // If an error occurred during the download, mark it as failed
                if (m_downloadStatus == DownloadStatus::Downloading)
                {
                    m_downloadStatus = DownloadStatus::Failed;
                    cout << "download failed, error: " << curl_easy_strerror(res) << endl;
                }
                else
                {
                    cout << "download interrupted, error: " << curl_easy_strerror(res) << endl;
                }

                if (nullptr != m_downloadCallback)
                {
                    m_downloadCallback(m_downloadProgress, m_downloadStatus);
                }
            }
            else
            {
                // If the download progress is 100%, then the download is successful
                if (100 == m_downloadProgress)
                {
                    m_downloadStatus = DownloadStatus::Completed;
                    cout << "download success " << endl;
                }
            }
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::progressCallback
         * @brief Download progress callback function
         * @param[in] clientp : Callback function parameter, i.e., this pointer
         * @param[in] dltotal : Total size of the download file
         * @param[in] dlnow : Size of the file already downloaded
         * @param[in] ultotal : Total size of the upload file
         * @param[in] ulnow : Size of the file already uploaded
         * @param[out] None
         * @retval None
         * @author  ZhangJianCe
         *******************************************************************************/
        int HttpsDownloadTask::progressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
        {
            CURLcode ret = CURLE_OK;
            HttpsDownloadTask *task = static_cast<HttpsDownloadTask *>(clientp);

            if (task->m_downloadStatus == DownloadStatus::Paused)
            {
                cout << "Download paused " << endl;
                return 1;
            }

            if (task->m_downloadStatus == DownloadStatus::Canceled)
            {
                cout << "Download cancelled " << endl;
                return 1;
            }

            // Get HTTP status code
            ret = curl_easy_getinfo(task->m_curl, CURLINFO_RESPONSE_CODE, &task->m_httpstatus);
            if (CURLE_OK == ret)
            {
                // If the HTTP status code is above 400, an error occurred during the download
                if (task->m_httpstatus < 400)
                {
                    if (dltotal > 0)
                    {
                        // Calculate the size of the file already downloaded. For resumable downloads, you need to add the size of the local file to get the real size of the file already downloaded
                        task->m_downloadedSize = dlnow + task->m_resumeFrom;
                        if (0 == task->m_fileSize)
                        {
                            task->m_downloadProgress = 0;
                        }
                        else
                        {
                            // Calculate download progress
                            task->m_downloadProgress = static_cast<int>(task->m_downloadedSize * 100ll / task->m_fileSize);
                        }
                        if (task->m_lastDownloadProgress != task->m_downloadProgress)
                        {
                            task->m_downloadStatus = DownloadStatus::Downloading;
                            if (nullptr != task->m_downloadCallback)
                            {
                                // Call download progress callback
                                task->m_downloadCallback(task->m_downloadProgress, task->m_downloadStatus);
                            }
                            task->m_lastDownloadProgress = task->m_downloadProgress;
                        }
                    }
                    else
                    {
                        task->m_downloadProgress = 0;
                    }
                }
            }
            return 0;
        }

        /*******************************************************************************
         * @name HttpsDownloadTask::writeCallback
         * @brief Internal download receive callback function
         * @param[in] ptr : Pointer to the receive buffer
         * @param[in] size : Size of the receive buffer
         * @param[in] nmemb : Number of receive buffers
         * @param[in] userdata : Callback function parameter, i.e., this pointer
         * @retval Size of the written file in bytes. If the return value is not equal to size*nmemb, it will cause the download to fail and report a write error
         * @author  ZhangJianCe
         *******************************************************************************/
        size_t HttpsDownloadTask::writeCallback(void *ptr, size_t size, size_t nmemb, void *userdata)
        {
            HttpsDownloadTask *task = static_cast<HttpsDownloadTask *>(userdata);

            if (task->m_downloadStatus == DownloadStatus::Canceled)
            {
                cout << "Download cancelled " << endl;
                return 0u;
            }
            if (task->m_downloadStatus == DownloadStatus::Paused)
            {
                cout << "Download paused " << endl;
                return 0u;
            }

            size_t written = fwrite(ptr, size, nmemb, task->m_file);

            // Synchronize the file buffer to the disk to prevent burst writes. The size of the write is proportional to the download speed limit
            if ((task->m_isFsyncEnabled) && (task->m_downloadedSize >= task->m_FsyncSize))
            {
                static_cast<void>(fflush(task->m_file));
                static_cast<void>(fsync(fileno(task->m_file)));
                task->m_FsyncSize = task->m_downloadedSize + (task->m_downloadSpeedLimit / 10);
            }

            return written;
        }

    }
}